/*
   *1.本程序适用微雪4.2寸三色屏,如果是其它品牌的三色屏，修改对应的三色屏display对象及对应接线方式即可。
   *2.通用驱动板接线：
     *busy---4
     *res---rx2
     *dc---tx2
     *cs---5
     *clk---18
     *din---23
     *gnd---gnd
     *vcc---3.3v
    *Version:2.2
    *Author：夏襄居士
    *Modefy：MKing
    *UploadTime：2022年1月24日15:47
    *已知bug
    * 1.轻度休眠重启后有概率导致联网失败，可采取delay方式延迟，但相应的，功耗会增加
    * 2.强制门户认证有概率无法正常进入，可能跟手机有关
    * 3.知乎热榜有概率显示过线，这个与接口返回的json数据有关，后期将更新接口以修复
*/
#include <U8g2_for_Adafruit_GFX.h>
#include <GxEPD2_3C.h>
#include <ArduinoJson.h>
#include <Timezone.h>
#define COROGOO_BOARD
#ifdef COROGOO_BOARD
#define PIN_SPI_SCK  18
#define PIN_SPI_DIN  23
#define PIN_SPI_CS   5
#define PIN_SPI_BUSY 17 //0: busy, 1: idle
#define PIN_SPI_RST  16
#define PIN_SPI_DC   19

#define PIN_KEY1    34
#define PIN_KEY2    35
#define PIN_KEY3    32
#define GPIO_NUM_KEY1    GPIO_NUM_34
#define GPIO_NUM_KEY2    GPIO_NUM_35
#define GPIO_NUM_KEY3    GPIO_NUM_32
#define isKEY_ON(KEY)    (digitalRead(KEY)==0)
#endif
//GxEPD2_3C<GxEPD2_420c, GxEPD2_420c::HEIGHT> display(GxEPD2_420c(/*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // GDEW042Z15
GxEPD2_3C<GxEPD2_420c, GxEPD2_420c::HEIGHT> display(GxEPD2_420c(/*CS=5*/ SS, /*DC=*/ 19, /*RST=*/ 16, /*BUSY=*/ 17)); // GDEW042Z15
#include "gb2312.c"
#include "imagedata.h"
#include "Webserver.h"
#include "NTP.h"
#include <Adafruit_SHT31.h>
#include <driver/adc.h>
#include <esp_adc_cal.h>
extern const uint8_t chinese_city_gb2312[239032] U8G2_FONT_SECTION("chinese_city_gb2312");
U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;
//****** HTTP服务器配置 ******
String language = "zh-Hans"; // 请求语言
String url_tips = "https://v1.hitokoto.cn/";//TIPS 获取地址
String url_ActualWeather;   //实况天气地址
String url_FutureWeather;   //未来天气地址
//****** 天气数据
//我们要从此网页中提取的数据的类型
struct ActualWeather
{
  char status_code[64];  //错误代码
  char city[16];         //城市名称
  char weather_name[16]; //天气现象名称
  char weather_code[4];  //天气现象代码
  char temp[5];          //温度
  char last_update[25];  //最后更新时间
};
ActualWeather actual;  //创建结构体变量 目前的

struct FutureWeather
{
  char status_code[64];       //错误代码

  char date0[14];             //今天日期
  char date0_text_day[20];    //白天天气现象名称
  char date0_code_day[4];     //白天天气现象代码
  char date0_text_night[16];  //晚上天气现象名称
  char date0_code_night[4];   //晚上天气现象代码
  char date0_high[5];         //最高温度
  char date0_low[5];          //最低温度
  char date0_humidity[5];     //相对湿度
  char date0_wind_scale[5];   //风力等级

  char date1[14];             //明天日期
  char date1_text_day[20];    //白天天气现象名称
  char date1_code_day[4];     //白天天气现象代码
  char date1_text_night[16];  //晚上天气现象名称
  char date1_code_night[4];   //晚上天气现象代码
  char date1_high[5];         //最高温度
  char date1_low[5];          //最低温度
  //char date1_humidity[5];     //相对湿度

  char date2[14];             //后天日期
  char date2_text_day[20];    //白天天气现象名称
  char date2_code_day[4];     //白天天气现象代码
  char date2_text_night[16];  //晚上天气现象名称
  char date2_code_night[4];   //晚上天气现象代码
  char date2_high[5];         //最高温度
  char date2_low[5];          //最低温度
  //char date2_humidity[5];     //相对湿度
};
FutureWeather future; //创建结构体变量 未来
struct LifeIndex //生活指数
{
  char status_code[64];  //错误代码
  char uvi[10];          //紫外线指数
}; LifeIndex life_index; //创建结构体变量 未来
struct News  //新闻API
{
  char status_code[64]; //错误代码
  char title[11][64];
};
struct Hitokoto  //TIPS API
{
  char status_code[64]; //错误代码
  char hitokoto[64];
};
//****** 一些变量 ******
String webServer_news = " ";
uint8_t client_count = 0;  //连接服务器的超时计数,暂未使用
uint8_t client_error = 0;  //错误代码,暂未使用
boolean night_updated = 1; //夜间更新 1-不更新 0-更新
//RTC临时数据
#define RTC_hour_dz 0           //小时地址
#define RTC_night_count_dz 1    //夜间计数地址
#define RTC_peiwang_state_dz 2  //配网状态地址
uint32_t RTC_hour = 100;        //小时
uint32_t RTC_night_count = 0;   //24-6点，夜间不更新计数
int32_t night_count_max = 0;    //需要跳过几次
uint32_t RTC_peiwang_state = 0; //配网状态 1-需要
//int daydate;//当天日期
Hitokoto tips; //创建结构体变量 TIPS
News newspaper; //创建结构体变量 新闻
int udc=0;//更新次数记录
bool isShowIPAddr = false;

void setup()
{
  Serial.begin(115200);
  Serial.print("Esp get free heap size: ");
  Serial.println(ESP.getFreeHeap());
  delay(10);
  EEPROM.begin(4096);
#ifdef COROGOO_BOARD
  /* Initialization for key ----------------------------------------------*/
  pinMode(PIN_KEY1,  INPUT);
  pinMode(PIN_KEY2, INPUT);
  pinMode(PIN_KEY3 , INPUT);

  Serial.println("Spi init VSPI!");
  delay(10);
  display.init(115200);
#else
  Serial.println("Spi init SPI!");
  delay(10);
  display.init();
#endif
  Serial.println("u8g2Fonts reg!");
  u8g2Fonts.begin(display);//将u8g2连接到display
  display.firstPage();
  display.display(1); 
  wifi_init();
}

void loop() {
ON_LOOP:
  uint32_t updateTick = 0;
  /*后台配置*/
  dnsServer.processNextRequest(); //处理来自客户端的请求
  webServer.handleClient();
  if (!settingMode) {
    GetData();
    initNTP();
    Serial.println("display fillScreen!");
    display.fillScreen(GxEPD_WHITE);
    get_weather();
    updatetime();
    hitokoto();
    newsdisplay();
    calendarInfodisplay();
    getTempAndHumi();
    if(isShowIPAddr)
      getSSIDAndIp();
    getVBat();
    udc++;
    Serial.println("display nextPage!");
    display.nextPage();
    Serial.print("Esp get free heap size: ");
    Serial.println(ESP.getFreeHeap());

#define IDLE_ON_SLEEP
#ifdef IDLE_ON_SLEEP
    /* 1.轻度休眠会关闭WiFi蓝牙以降低功耗，所以唤醒后需要重新联网
     * 2.之所以不用功耗更低的深度休眠，因为深度休眠只保留RTC，内存
     *   中的数据会丢失
     * 3.休眠函数中时间单位是微秒，所以数据要X1000000
    */
    updateTick = 0;
    while(isShowIPAddr && updateTick <= 60*1000)
    {
        delay(100);
        updateTick += 100;
        dnsServer.processNextRequest(); //处理来自客户端的请求
        webServer.handleClient(); //等待60秒WEB配置
        if(onSettingCalendarInfo)
        {
            onSettingCalendarInfo = false;
            updateTick = updateTick > 20*1000 ? updateTick-20*1000: updateTick; //延缓20秒再休眠
        }
#ifdef COROGOO_BOARD
        if(isKEY_ON(PIN_KEY1))
        {
            isShowIPAddr = false;
            Serial.println("KEY1 On.");
            goto ON_LOOP;
        }
        if(isKEY_ON(PIN_KEY2))
        {
            isShowIPAddr = true;
            Serial.println("KEY2 On.");
            goto ON_LOOP;
        }
        if(isKEY_ON(PIN_KEY3))
        {
            //Serial.println("KEY3 On.");
            //goto ON_LOOP;
        }
#endif
    }
    struct timeval sleep_enter_time;
    const int wakeup_time_sec = getScreenCycleMins()*60; //X分钟刷新一次
    Serial.printf("Enabling timer wakeup: %ds\r\n", wakeup_time_sec);
    esp_sleep_enable_timer_wakeup(wakeup_time_sec * 1000*1000);
    const int ext_wakeup_pin_1 = PIN_KEY2;//使能25脚为唤醒外部中断1
    const uint64_t ext_wakeup_pin_1_mask = 1ULL << ext_wakeup_pin_1;
    const int ext_wakeup_pin_2 = PIN_KEY3;//时能26脚为唤醒外部中断2
    const uint64_t ext_wakeup_pin_2_mask = 1ULL << ext_wakeup_pin_2;
    Serial.printf("Enabling EXT1 wakeup on pins: GPIO%d, GPIO%d\r\n", ext_wakeup_pin_1, ext_wakeup_pin_2);
    //使能外部中断唤醒功能
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_KEY1, LOW);
    esp_sleep_enable_ext1_wakeup(ext_wakeup_pin_1_mask | ext_wakeup_pin_2_mask, ESP_EXT1_WAKEUP_ALL_LOW);
    // Isolate GPIO12 pin from external circuits. This is needed for modules
    // which have an external pull-up resistor on GPIO12 (such as ESP32-WROVER)
    // to minimize current consumption.
    //rtc_gpio_isolate(GPIO_NUM_12);//将12脚隔离
    Serial.printf("Entering light sleep\r\n");
    delay(50);
    gettimeofday(&sleep_enter_time, NULL);//获取进入睡眠的时间
    esp_light_sleep_start();
    //esp_sleep_get_wakeup_cause 函数的功能就是根据寄存器判断esp32是否是从低功耗模式中唤醒出来，
    //是被那种唤醒源唤醒的
    esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
    switch (wakeup_cause) {
        case ESP_SLEEP_WAKEUP_EXT1: {//外部中断1 唤醒
            uint64_t wakeup_pin_mask = esp_sleep_get_ext1_wakeup_status();
            if (wakeup_pin_mask != 0) {
                int pin = __builtin_ffsll(wakeup_pin_mask) - 1;
                Serial.printf("Wake up from EXT1 GPIO %d\r\n", pin);
                if(pin == PIN_KEY1)
                {
                    isShowIPAddr = false;
                }
                if(pin == PIN_KEY2)
                {
                    isShowIPAddr = true;
                }
            } else {
                Serial.printf("Wake up from EXT1 GPIO\r\n");
                isShowIPAddr = true;
            }
        }
        break;
        case ESP_SLEEP_WAKEUP_EXT0:
        case ESP_SLEEP_WAKEUP_GPIO: {//外部GPIO 唤醒
            Serial.printf("Wake up from EXT0 GPIO\r\n");
            isShowIPAddr = false;
        }
        break;
        case ESP_SLEEP_WAKEUP_TIMER: {//定时器唤醒
            struct timeval now;
            gettimeofday(&now, NULL);//获取当前时间, 格式为s和us
            //当前时间减去进入睡眠时间就是睡眠所消耗的时间, 乘以1000转换成ms
            int sleep_time_ms = (now.tv_sec - sleep_enter_time.tv_sec) * 1000 + (now.tv_usec - sleep_enter_time.tv_usec) / 1000;
            Serial.printf("Wake up from timer. Time spent in deep sleep: %dms\r\n", sleep_time_ms);
        }
        break;
        case ESP_SLEEP_WAKEUP_UNDEFINED://不是唤醒 正常执行
        default:
            Serial.printf("Not a deep sleep reset, wakeup cause %d\r\n", wakeup_cause);
        break;
    }
    delay(1000);
    wifi_init();//休眠后需要重新联网
    dnsServer.processNextRequest();
 #else
    updateTick = 0;
    while(updateTick <= 300000)
    {
        delay(100);//5分钟更新一次
        updateTick += 100;
        dnsServer.processNextRequest(); //处理来自客户端的请求
        webServer.handleClient();
#ifdef COROGOO_BOARD
        if(isKEY_ON(PIN_KEY1))
        {
            //OnWebServer(false);
            isShowIPAddr = false;
            Serial.println("KEY1 On.");
            break;
        }
        if(isKEY_ON(PIN_KEY2))
        {
            //OnWebServer(true);
            isShowIPAddr = true;
            Serial.println("KEY2 On.");
            break;
        }
        if(isKEY_ON(PIN_KEY3))
        {
            //Serial.println("KEY3 On.");
            //break;
        }
#endif
    }
#endif
  }
#if 1
  else
  {
    bool key_choice = false;
#ifdef COROGOO_BOARD
      updateTick = 0;
      while(updateTick <= 180000){
        dnsServer.processNextRequest();
        webServer.handleClient();
        if(isKEY_ON(PIN_KEY1))
        {
          Serial.println("KEY1 On.");
          key_choice = true;
          break;
        }
        delay(100);
        updateTick += 100;
      }
#endif
      if(key_choice)
      {
          Serial.println("display wifi AP info!");
          wifi_apcfg_notice_display();
          udc++;
          updateTick = 0;
          while(updateTick <= 60000)
          {
              dnsServer.processNextRequest();
              webServer.handleClient();
              if(!settingMode) break;
              delay(100);
              updateTick += 100;
          }
      }
  }
#endif
      /*深度睡眠会导致内存中数据丢失让新闻切换出问题，故弃用
       * esp_sleep_enable_timer_wakeup(20000000);
      esp_deep_sleep_start();
      */
  };
